package org.shagin.jfin.budget;

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;

import org.shagin.jfin.core.Debug;
import org.shagin.jfin.core.CoreUtils;
import org.shagin.jfin.data.model.Account;
import org.shagin.jfin.data.model.CostCenter;
import org.shagin.jfin.data.model.IBudget;
import org.shagin.jfin.data.model.OperationCategory;
import org.shagin.jfin.data.model.OperationCategoryLeaf;
import org.shagin.jfin.data.model.OperationRule;
import org.shagin.jfin.data.model.OperationRuleAction;
import org.shagin.jfin.data.model.TransferRule;
import org.shagin.jfin.date.DateUtils;
import org.shagin.xml.XMLUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

public class BudgetXMLWriter extends BudgetWriter implements IBudgetXML {

    private static final Logger log = LoggerFactory
            .getLogger(BudgetXMLReader.class);

    private String fileName;

    private Document doc;
    private Element rootElement;

    private IBudget budget;

    /**
     * Instantiates a new budget xml writer.
     * 
     * @param fileName the file name
     */
    public BudgetXMLWriter(String fileName) {
        CoreUtils.checkNotNull(fileName, "fileName");
        this.fileName = fileName;
    }

    @Override
    public void write(IBudget budget) {
        Debug.traceEnter(log);
        CoreUtils.checkNotNull(budget, "budget");
        this.budget = budget;
        try {
            doc = XMLUtils.newDocument();
            saveRootAttributes();
            saveCostCenters();
            saveAccounts();
            saveIncomes();
            saveExpenses();
            saveTrasfers();
            saveXml();
        } catch (ParserConfigurationException e) {
            e.printStackTrace();
        }
    }

    /**
     * Save xml.
     */
    private void saveXml() {
        Debug.traceEnter(log);
        CoreUtils.assertNotNull(doc, "doc");
        try {
            XMLUtils.saveXmlDocument(doc, fileName);
        } catch (TransformerException e) {
            Debug.error(log, e);
        }
    }

    /**
     * Save root attributes.
     */
    private void saveRootAttributes() {
        Debug.traceEnter(log);
        CoreUtils.assertNotNull(doc, "doc");
        CoreUtils.assertNotNull(budget, "budget");
        rootElement = doc.createElement("budget");
        doc.appendChild(rootElement);
        XMLUtils.setNodeAttribute(doc, rootElement, "id", budget.getId());
        XMLUtils.setNodeAttribute(doc, rootElement, "from",
                DateUtils.format(budget.getDateRange().getStart()));
        XMLUtils.setNodeAttribute(doc, rootElement, "to",
                DateUtils.format(budget.getDateRange().getEnd()));

    }

    /**
     * Save cost centers.
     */
    private void saveCostCenters() {
        CoreUtils.assertNotNull(doc, "doc");
        CoreUtils.assertNotNull(budget, "budget");
        CoreUtils.assertNotNull(rootElement, "rootElement");
        Element ссElems = XMLUtils.addElement(doc, rootElement, "CostCenters");
        boolean def = true;
        for (CostCenter cc : budget.getCostCenterDomain().getAll()) {
            Element ссElem = XMLUtils.addElement(doc, ссElems, "CostCenter");
            XMLUtils.setNodeAttribute(doc, ссElem, "id", cc.getId());
            if (def) {
                XMLUtils.setNodeAttribute(doc, ссElem, "default", "true");
                def = false;
            }
        }
    }

    /**
     * Save accounts.
     */
    private void saveAccounts() {
        CoreUtils.assertNotNull(doc, "doc");
        CoreUtils.assertNotNull(budget, "budget");
        CoreUtils.assertNotNull(rootElement, "rootElement");
        Element aссElems = XMLUtils.addElement(doc, rootElement, "Accounts");
        boolean def = true;
        for (Account acc : budget.getAccountDomain().getAll()) {
            Element aссElem = XMLUtils.addElement(doc, aссElems, "Account");
            XMLUtils.setNodeAttribute(doc, aссElem, "id", acc.getId());
            XMLUtils.setNodeAttribute(doc, aссElem, "value", acc.getAmount()
                    .getValue() + "");
            XMLUtils.setNodeAttribute(doc, aссElem, "CostCenter", acc
                    .getCostCenter().getId());
            if (def) {
                XMLUtils.setNodeAttribute(doc, aссElem, "default", "true");
                def = false;
            }
        }
    }

    /**
     * Save incomes.
     */
    private void saveIncomes() {
        CoreUtils.assertNotNull(doc, "doc");
        CoreUtils.assertNotNull(budget, "budget");
        CoreUtils.assertNotNull(rootElement, "rootElement");
        for (OperationCategory oc : OperationRule.getRoots(budget
                .getOperationRuleDomain().getIncomes())) {
            saveOperationCategory("incomes", rootElement, oc);
        }
    }

    /**
     * Save expenses.
     */
    private void saveExpenses() {
        CoreUtils.assertNotNull(doc, "doc");
        CoreUtils.assertNotNull(budget, "budget");
        CoreUtils.assertNotNull(rootElement, "rootElement");
        for (OperationCategory oc : OperationRule.getRoots(budget
                .getOperationRuleDomain().getExpenses())) {
            saveOperationCategory("expenses", rootElement, oc);
        }
    }

    /**
     * Save expense.
     * 
     * @param parent the parent
     * @param oc the oc
     */
    private void saveOperationCategory(String tag, Element parent,
            OperationCategory oc) {
        Element elem = XMLUtils.addElement(doc, parent, tag);
        XMLUtils.setNodeAttribute(doc, elem, "id", oc.getId());
        if (oc instanceof OperationCategoryLeaf) {
            saveOperationCategoryLeaf(elem, (OperationCategoryLeaf) oc);
        } else
            for (OperationCategory ocChild : oc.getChilds()) {
                saveOperationCategory(tag, elem, ocChild);
            }
    }

    /**
     * Save operation category leaf.
     * 
     * @param parent the parent
     * @param oc the oc
     */
    private void saveOperationCategoryLeaf(Element parent,
            OperationCategoryLeaf oc) {
        for (OperationRule rule : oc.getRules()) {
            saveRule(parent, rule);
        }
    }

    /**
     * Save rule.
     * 
     * @param parent the parent
     * @param rule the rule
     */
    private void saveRule(Element parent, OperationRule rule) {
        Element elem = XMLUtils.addElement(doc, parent, "rule");
        XMLUtils.setNodeAttribute(doc, elem, "id", rule.getId());
        XMLUtils.setNodeAttribute(doc, elem, "Account", rule.getSourceAccount()
                .getId());
        if (rule instanceof TransferRule) {
            TransferRule tr = (TransferRule) rule;
            XMLUtils.setNodeAttribute(doc, elem, "ToAccount", tr
                    .getDestAccount().getId());
        }

        for (OperationRuleAction act : rule.getActions()) {
            Element e = XMLUtils.addElement(doc, elem, "when");

            XMLUtils.setNodeAttribute(doc, e, "type", act.getDateCondition()
                    .format());
            XMLUtils.setNodeAttribute(doc, e, "value", act.getMoney()
                    .getValue() + "");

            if (!act.getDateCondition().getRange().isCloseDate())
                XMLUtils.setNodeAttribute(
                        doc,
                        e,
                        "from",
                        DateUtils.format(act.getDateCondition().getRange()
                                .getStart()));

            if (!act.getDateCondition().getRange().isOpenDate())
                XMLUtils.setNodeAttribute(
                        doc,
                        e,
                        "to",
                        DateUtils.format(act.getDateCondition().getRange()
                                .getEnd()));
        }

    }

    /**
     * Save trasfers.
     */
    private void saveTrasfers() {
        CoreUtils.assertNotNull(doc, "doc");
        CoreUtils.assertNotNull(budget, "budget");
        CoreUtils.assertNotNull(rootElement, "rootElement");
        for (OperationCategory oc : OperationRule.getRoots(budget
                .getOperationRuleDomain().getTrasfers())) {
            saveOperationCategory("transfers", rootElement, oc);
        }
    }
}
